package com.letu.core.utils;

import lombok.extern.slf4j.Slf4j;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;

@Slf4j
public class ClassUtils {
    private static final String TYPE_CLASS_NAME_PREFIX = "class ";
    private static final String TYPE_INTERFACE_NAME_PREFIX = "interface ";

    public static <T> Class<T> forName(String name) {
        Class<T> clazz = null;
        try {
            clazz = (Class<T>) Class.forName(name);
        } catch (ClassNotFoundException e) {
            log.error(e.getMessage());
        }
        return clazz;
    }

    public static String getClassName(Type type) {
        if (type == null) {
            return "";
        }
        String className = type.toString();
        if (className.startsWith(TYPE_CLASS_NAME_PREFIX)) {
            className = className.substring(TYPE_CLASS_NAME_PREFIX.length());
        } else if (className.startsWith(TYPE_INTERFACE_NAME_PREFIX)) {
            className = className.substring(TYPE_INTERFACE_NAME_PREFIX.length());
        }
        return className;
    }

    public static Class<?> getClass(Type type)
            throws ClassNotFoundException {
        String className = getClassName(type);
        if (className == null || className.isEmpty()) {
            return null;
        }
        return Class.forName(className);
    }

    public static Method getPublicMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
        Method method = null;
        try {
            method = clazz.getMethod(methodName, parameterTypes);
        } catch (NoSuchMethodException e) {
            log.error(methodName, e);
        } catch (SecurityException e) {
            log.error(methodName, e);
        }
        return method;
    }

    public static Method getPrivateMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
        Method method = null;
        try {
            method = clazz.getDeclaredMethod(methodName, parameterTypes);
        } catch (NoSuchMethodException e) {
            log.debug(methodName, e);
        } catch (SecurityException e) {
            log.debug(methodName, e);
        }
        return method;
    }

    public static Method[] getPublicMethods(Class<?> clazz) {
        Method[] methodArr = null;
        methodArr = clazz.getMethods();
        return methodArr;
    }

    public static Method[] getPrivateMethods(Class<?> clazz) {
        Method[] methodArr = null;
        methodArr = clazz.getDeclaredMethods();
        return methodArr;
    }

    public static void invoke(Method method, Object object, Object... params) {
        try {
            method.invoke(object, params);
        } catch (IllegalAccessException e) {
            log.error(e.getMessage());
        } catch (IllegalArgumentException e) {
            log.error(e.getMessage());
        } catch (InvocationTargetException e) {
            log.error(e.getMessage());
        }
    }

    public static Field getPublicField(Class<?> clazz, String fieldName) {
        Field field = null;
        try {
            field = clazz.getField(fieldName);
        } catch (NoSuchFieldException e) {
            log.error(e.getMessage());
        } catch (SecurityException e) {
            log.error(e.getMessage());
        }
        return field;
    }

    public static Field getPrivateField(Class<?> clazz, String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            log.error(e.getMessage());
        } catch (SecurityException e) {
            log.error(e.getMessage());
        }
        return field;
    }

    public static Field[] getPublicFields(Class<?> clazz) {
        Field[] fields = null;
        fields = clazz.getFields();
        return fields;
    }

    public static Field[] getPrivateFields(Class<?> clazz) {
        Field[] fields = null;
        fields = clazz.getDeclaredFields();
        return fields;
    }

    public static Annotation[] getPublicMethodAnnotations(Method method) {
        Annotation[] anno = null;
        anno = method.getAnnotations();
        return anno;
    }

    public static Annotation[] getPrivateMethodAnnotations(Method method) {
        Annotation[] anno = null;
        anno = method.getDeclaredAnnotations();
        return anno;
    }

    public static Annotation getMethodAnnotation(Method method, Class clazz) {
        Annotation anno = null;
        anno = method.getAnnotation(clazz);
        return anno;
    }

    public static Annotation[] getPublicAnnotations(Field field) {
        Annotation[] anno = null;
        anno = field.getAnnotations();
        return anno;
    }

    public static Annotation[] getPrivateAnnotations(Field field) {
        Annotation[] anno = null;
        anno = field.getDeclaredAnnotations();
        return anno;
    }

    public static Annotation getAnnotations(Field field, Class clazz) {
        Annotation anno = null;
        anno = field.getAnnotation(clazz);
        return anno;
    }

    public static <T> Constructor<T> getConstructor(Class<T> clazz, Class<?>... paramterClass) {
        Constructor<T> constructor = null;
        try {
            constructor = clazz.getConstructor(paramterClass);
        } catch (NoSuchMethodException e) {
            log.error(e.getMessage());
        } catch (SecurityException e) {
            log.error(e.getMessage());
        }
        return constructor;
    }

    public static <T> Object ConstructorNewInstance(Constructor<T> con, Object... value) {
        Object obj = null;
        for (Object object : value) {
            System.out.println(object.getClass());
        }
        try {
            obj = con.newInstance(value);
        } catch (InstantiationException e) {
            log.error(e.getMessage());
        } catch (IllegalAccessException e) {
            log.error(e.getMessage());
        } catch (IllegalArgumentException e) {
            log.error(e.getMessage());
        } catch (InvocationTargetException e) {
            log.error(e.getMessage());
        }
        return obj;
    }

    public static Class<?> getMethodReturnGenericClass(Method method) {
        Type returnType = method.getGenericReturnType();
        Class<?> returnClass = null;
        if (returnType instanceof ParameterizedType) {
            ParameterizedType type = (ParameterizedType) returnType;
            Type[] typeArguments = type.getActualTypeArguments();
            for (Type typeArgument : typeArguments) {
                returnClass = (Class) typeArgument;
            }
        }
        return returnClass;
    }

    public static Class<?> getMethodParamGenericClass(Method method) {
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        Class parameterArgClass = null;
        for (Type genericParameterType : genericParameterTypes) {
            if (genericParameterType instanceof ParameterizedType) {
                ParameterizedType aType = (ParameterizedType) genericParameterType;
                Type[] parameterArgTypes = aType.getActualTypeArguments();
                for (Type parameterArgType : parameterArgTypes) {
                    parameterArgClass = (Class) parameterArgType;
                }
            }
        }
        return parameterArgClass;
    }

    public static Class<?> getMethodFieldGenericClass(Field field) {
        Type genericFieldType = field.getGenericType();
        Class fieldArgClass = null;
        if (genericFieldType instanceof ParameterizedType) {
            ParameterizedType aType = (ParameterizedType) genericFieldType;
            Type[] fieldArgTypes = aType.getActualTypeArguments();
            for (Type fieldArgType : fieldArgTypes) {
                fieldArgClass = (Class) fieldArgType;
//		        System.out.println("fieldArgClass = " + fieldArgClass);
            }
        }
        return fieldArgClass;
    }

    public static Object getFieldValue(Object obj, Field field) {
        Object value = null;
        try {
            field.setAccessible(true);
            value = field.get(obj);
        } catch (IllegalArgumentException e) {
            log.error(e.getMessage());
            return null;
        } catch (IllegalAccessException e) {
            log.error(e.getMessage());
            return null;
        }
        return value;
    }

    public static Object setFieldValue(Object bean, Field field, String value, Class<?> valueType) {
        field.setAccessible(true);
        if (valueType == Date.class) {
            setFieldValueDate(bean, field, value);
        } else {
            setFieldValueBase(bean, field, value, valueType);
        }
        return bean;
    }

    public static Object setFieldValue(Object bean, Field field, Object value) {
        try {
            field.set(bean, value);
        } catch (IllegalArgumentException e) {
            log.error(e.getMessage());
        } catch (IllegalAccessException e) {
            log.error(e.getMessage());
        }
        return bean;
    }

    public static Object setFieldValueDate(Object bean, Field field, String value) {
        SimpleDateFormat datesdf = new SimpleDateFormat("yyyy-MM-dd");
        Date date = null;
        try {
            date = datesdf.parse(value);
        } catch (ParseException e) {
            log.error(e.getMessage());
        }
        try {
            field.set(bean, date);
        } catch (IllegalArgumentException e) {
            log.error(e.getMessage());
        } catch (IllegalAccessException e) {
            log.error(e.getMessage());
        }
        return bean;
    }

    public static <T> Object setFieldValueBase(Object bean, Field field, String value, Class<T> valueType) {
        Constructor<T> con = getConstructor(valueType, String.class);
        Object param = ConstructorNewInstance(con, value);
        try {
            field.set(bean, param);
        } catch (IllegalArgumentException e) {
            log.error(e.getMessage());
        } catch (IllegalAccessException e) {
            log.error(e.getMessage());
        }
        return bean;
    }

    /**
     * 如果target内的字段值为空，则取source内的字段替换
     *
     * @param source
     * @param target
     * @param <S>
     * @param <T>
     */
    public static <S, T> void replaceIfNull(S source, T target) {
        Field[] fields = getPrivateFields(target.getClass());
        Field field;
        for (int i = 0; i < fields.length; i++) {
            field = fields[i];
            Object sourceValue = getFieldValue(source, field);
            Object targetValue = getFieldValue(target, field);
            if (Objects.isNull(targetValue) && Objects.nonNull(sourceValue)) {
                setFieldValue(target, field, sourceValue);
            }

        }
    }
}
